package com.zues.thread.aqs;

import com.zues.thread.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 测试Semaphore信号灯
 * @author zues
 * @date 4/1/2025 9:24 下午
 */
@Slf4j(topic = "c.SemaphoreDemo1")
public class SemaphoreDemo1 {

    public static void main(String[] args) {

//        dbConnectionPoolExample();

//        rateLimiterExample();

        sequenceControlExample();
    }

    /**
     * 测试Semaphore信号灯
     * 模拟一个停车场，有3个停车位，10辆车来停车。
     * 当有车离开时，停车位会被释放，其他车可以继续停车。
     * 这个例子中，Semaphore的初始许可数量为3，表示有3个可用的停车位。
     */
    private static void test1() {
        // 创建一个Semaphore实例，初始许可数量为3，表示有3个可用资源
        Semaphore semaphore = new Semaphore(3);

        // 循环创建10个线程来模拟多个任务竞争资源
        for (int i = 0; i < 10; i++) {
            // 创建一个新线程
            new Thread(() -> {
                try {
                    // 线程尝试获取一个许可，如果没有可用许可，线程会被阻塞
                    semaphore.acquire();
                    // 打印线程名称，表示该线程成功获取到资源
                    log.debug("抢到车位");
                    // 线程休眠2秒，模拟线程占用资源的操作
                    ThreadUtil.sleep(2);
                } catch (InterruptedException e) {
                    // 当线程在等待获取许可的过程中被中断，捕获中断异常并重新抛出运行时异常
                    throw new RuntimeException(e);
                } finally {
                    // 无论线程是否正常执行，都要释放许可，确保资源的正确释放
                    semaphore.release();
                }
            }).start();
        }
    }

    /**
     * 模拟数据库连接池
     * 假设我们有一个数据库连接池，其中有 3个数据库连接。
     * 我们有 10 个查询请求，每个请求需要一个数据库连接。
     * 我们使用 Semaphore 来限制同时可以有 3 个查询请求获取数据库连接。
     * 当有查询请求获取到连接时，Semaphore 的许可数量会减少，当连接释放时，许可数量会增加。
     * 这样可以确保同时只有 3个查询请求可以获取到数据库连接，避免了连接池被过度占用。
     * <p>
     *     Semaphore 是一个计数信号量，用于控制对共享资源的访问。
     *     它的构造函数接受一个许可数量作为参数，该许可数量表示可以同时访问共享资源的线程数量。
     *     当一个线程调用 acquire() 方法时，如果有可用的许可，该线程将获取到许可并继续执行；
     *     如果没有可用的许可，该线程将被阻塞，直到有其他线程释放了许可。
     *     当一个线程调用 release() 方法时，它将释放一个许可，使其他线程可以获取到许可并继续执行。
     *     Semaphore 还提供了 tryAcquire() 方法和 tryAcquire(long timeout, TimeUnit unit) 方法，
     *     用于在指定的时间内尝试获取许可，如果在指定时间内没有获取到许可，该方法将返回 false。
     *     Semaphore 还提供了 availablePermits() 方法，用于获取当前可用的许可数量。
     *     Semaphore 还提供了公平模式和非公平模式两种模式，公平模式下，线程将按照请求的顺序获取许可，而非公平模式下，线程将按照随机顺序获取许可。
     *
     */
    public static void dbConnectionPoolExample() {
        // 模拟 3 个数据库连接
        final int POOL_SIZE = 3;
        Semaphore connectionPool = new Semaphore(POOL_SIZE, true); // 公平模式

        // 模拟 10 个查询请求
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    connectionPool.acquire();
                    log.debug("任务{} 获取数据库连接 [剩余许可：{}]",
                            taskId, connectionPool.availablePermits());

                    // 模拟数据库操作
                    //随机数1-3
                    int random = new Random().nextInt(3) + 1;
                    ThreadUtil.sleep(random);

                } catch (InterruptedException e) {
                    log.error("数据库操作被中断");
                } finally {
                    connectionPool.release();
                    log.debug("任务{} 释放连接", taskId);
                }
            }).start();
        }
    }


    /**
     * 模拟一个简单的限流场景
     * 假设我们有一个 Web 服务，它每秒可以处理 5 个请求。
     * 我们有 20 个请求过来，我们使用 Semaphore 来限制每秒最多处理 5 个请求。
     * 当有请求过来时，Semaphore 的许可数量会减少，当请求处理完成时，许可数量会增加。
     * 这样可以确保每秒最多只有 5 个请求被处理，避免了请求过多导致的服务崩溃。
     * <p>
     */
    public static void rateLimiterExample() {
        // 限制每秒最多处理 5 个请求
        Semaphore rateLimiter = new Semaphore(5);

        // 模拟 20 个请求
        for (int i = 1; i <= 20; i++) {
            final int requestId = i;
            new Thread(() -> {
                try {
                    // 尝试在 1 秒内获取许可
                    if (rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
                        log.debug("处理请求 {} [剩余许可：{}]",
                                requestId, rateLimiter.availablePermits());

                        // 模拟业务处理
                        ThreadUtil.sleep(1);
                    } else {
                        log.warn("请求 {} 被限流", requestId);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    rateLimiter.release();
                }
            }).start();

            // 模拟请求间隔
//            ThreadUtil.sleep(2);
        }
    }

    /**
     * 控制线程执行顺序
     * 假设我们有三个线程 A、B、C，我们想让它们按照 A -> B -> C 的顺序执行。
     * 我们可以使用 Semaphore 来控制线程的执行顺序。
     * 具体来说，我们可以创建一个 Semaphore 实例，初始许可数量为 0，然后在每个线程执行前，
     * 调用 acquire() 方法获取一个许可，在每个线程执行后，调用 release() 方法释放一个许可。
     * 这样，当线程 A 执行完后，线程 B 就可以获取到许可，然后执行；
     * 当线程 B 执行完后，线程 C 就可以获取到许可，然后执行。
     */
    public static void sequenceControlExample() {
        Semaphore s1 = new Semaphore(0); // 初始无许可
        Semaphore s2 = new Semaphore(0);

        new Thread(() -> {
            log.debug("线程A执行任务1");
            s1.release(); // 释放许可给线程B
        }).start();

        new Thread(() -> {
            try {
                s1.acquire();
                log.debug("线程B执行任务2");
                s2.release(); // 释放许可给线程C
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                s2.acquire();
                log.debug("线程C执行任务3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }


}
